﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace MonoStrategy
{
    public partial class GameMap
    {
        private RaceConfig[] m_Races;
        private GameMapFile m_LogFile;
        private CrossRandom m_Random = new CrossRandom(0);
        private bool m_UpdateConfig = false;
        private bool m_ForwardOneMinute = false;
        private ManualResetEvent m_SuspendSignal = new ManualResetEvent(false);
        private ManualResetEvent m_ResumeSignal = new ManualResetEvent(false);
        private Thread m_SimulationThread;

        internal TerrainRouting Routing { get; set; }
        internal MovableManager MovMgr { get; private set; }
        internal ResourceManager ResMgr { get; private set; }
        internal BuildingManager BuildMgr { get; private set; }
        public bool IsSuspended { get; private set; }
        public long CurrentCycle { get { return MovMgr.CurrentCycle; } }
        public long CycleResolution { get { return MovMgr.CycleResolution; } }
        public long AnimationTime { get; private set; }
        public int Size { get; private set; }
        public TerrainDefinition Terrain { get; private set; }
        public GameConfig Config { get; private set; }
        public bool IsInitialized { get; private set; }
        public RaceConfig Race { get { return m_Races[0]; } }
        public long AvgPlanMillis { get { return MovMgr.AvgPlanMillis; } }
        public long MaxPlanMillis { get { return MovMgr.MaxPlanMillis; } }

        public event DNotifyHandler<GameMap> OnGameStarted;
        public event DOnAddMovable<GameMap> OnAddMovable;
        public event DOnRemoveMovable<GameMap> OnRemoveMovable;
        public event DOnAddBuilding<GameMap> OnAddBuilding;
        public event DOnRemoveBuilding<GameMap> OnRemoveBuilding;
        public event DOnAddResourceStack<GameMap> OnAddStack;
        public event DOnRemoveResourceStack<GameMap> OnRemoveStack;
        public event DOnGradingStep<GameMap> OnGradingStep;
        public event DOnAddBuildTask<GameMap> OnAddBuildTask;
        public event DOnRemoveBuildTask<GameMap> OnRemoveBuildTask;
        public event DOnAddFoilage<GameMap> OnAddFoilage;
        public event DOnRemoveFoilage<GameMap> OnRemoveFoilage;
        public event DOnAddStone<GameMap> OnAddStone;
        public event DOnRemoveStone<GameMap> OnRemoveStone;

        public GameMap(int inSize, int inInitialHouseSpaceCount, RaceConfig[] inRaces)
        {
            m_LogFile = GameMapFile.OpenWrite(Path.GetTempFileName(), null);
            m_Races = inRaces.ToArray();
            Size = inSize;
            Terrain = new TerrainDefinition(this, new XMLTerrainConfig());
            MovMgr = new MovableManager(this, 0, (int)CyclePoint.CYCLE_MILLIS);
            Routing = new TerrainRouting(Terrain, CurrentCycle, CycleResolution);
            ResMgr = new ResourceManager(this, MovMgr);
            BuildMgr = new BuildingManager(MovMgr, ResMgr, inInitialHouseSpaceCount);
            ResMgr.BuildMgr = BuildMgr;

            if (m_Races.Length == 0)
                throw new ArgumentException("At least one race config must be specified.");

            // register event proxies
            MovMgr.OnAddMovable += (sender, movable) => { 
                if (OnAddMovable != null) 
                    VisualUtilities.SynchronizeTask(() => OnAddMovable(this, movable));

                movable.OnStop += Movable_OnStop; 
            };
            MovMgr.OnRemoveMovable += (sender, movable) => { if (OnRemoveMovable != null) VisualUtilities.SynchronizeTask(() => OnRemoveMovable(this, movable)); };

            ResMgr.OnAddStack += (sender, stack) => { if (OnAddStack != null) VisualUtilities.SynchronizeTask(() => OnAddStack(this, stack)); };
            ResMgr.OnRemoveStack += (sender, stack) => { if (OnRemoveStack != null) VisualUtilities.SynchronizeTask(() => OnRemoveStack(this, stack)); };
            ResMgr.OnAddFoilage += (sender, foilage) => { if (OnAddBuildTask != null) VisualUtilities.SynchronizeTask(() => OnAddFoilage(this, foilage)); };
            ResMgr.OnRemoveFoilage += (sender, foilage) => { if (OnRemoveBuildTask != null) VisualUtilities.SynchronizeTask(() => OnRemoveFoilage(this, foilage)); };
            ResMgr.OnAddStone += (sender, stone) => { if (OnAddStone != null) VisualUtilities.SynchronizeTask(() => OnAddStone(this, stone)); };
            ResMgr.OnRemoveStone += (sender, stone) => { if (OnRemoveStone != null) VisualUtilities.SynchronizeTask(() => OnRemoveStone(this, stone)); };

            BuildMgr.OnAddBuilding += (sender, buildable) => { if (OnAddBuilding != null) VisualUtilities.SynchronizeTask(() => OnAddBuilding(this, buildable)); };
            BuildMgr.OnRemoveBuilding += (sender, buildable) => { if (OnRemoveBuilding != null) VisualUtilities.SynchronizeTask(() => OnRemoveBuilding(this, buildable)); };
            BuildMgr.OnGradingStep += (sender, grader, completion) => { if (OnGradingStep != null) VisualUtilities.SynchronizeTask(() => OnGradingStep(this, grader, completion)); };
            BuildMgr.OnAddBuildTask += (sender, task) => { if (OnAddBuildTask != null) VisualUtilities.SynchronizeTask(() => OnAddBuildTask(this, task)); };
            BuildMgr.OnRemoveBuildTask += (sender, task) => { if (OnRemoveBuildTask != null) VisualUtilities.SynchronizeTask(() => OnRemoveBuildTask(this, task)); };

            Config = new GameConfig(this);

            // prepare simulation thread...
            m_SimulationThread = new Thread(SimulationLoop);
            m_SimulationThread.IsBackground = true;
        }


        public void ForwardOneMinute()
        {
            m_ForwardOneMinute = true;
        }

        public void UpdateConfig()
        {
            m_UpdateConfig = true;
        }

        public void Initialize()
        {
            Start(null);
        }

        public void Start(Procedure inInitProcedure)
        {
            if (IsInitialized)
                throw new InvalidOperationException();

            if (inInitProcedure != null)
                inInitProcedure();

            IsInitialized = true;

            m_SimulationThread.Start();
        }

        public void GenerateRandomMap(long inSeed)
        {
            RandomTerrainGenerator.Run(this, 0);
        }

        public void GenerateMapFromFile(String inXMLFile, long inSeed)
        {
            String fullXMLPath = Path.GetFullPath(inXMLFile);
            String mapFile = Path.GetDirectoryName(inXMLFile) + "/" + Path.GetFileNameWithoutExtension(inXMLFile) + ".map";
            bool isLoaded = false;

            if (File.Exists(mapFile))
            {
                // load from map file
                FileStream stream = new FileStream(mapFile, FileMode.Open, FileAccess.Read, FileShare.Read);

                try
                {
                    using (stream)
                    {
                        Routing.LoadFromStream(stream);
                        Terrain.LoadFromStream(stream);
                    }

                    isLoaded = true;
                }
                catch
                {
                    File.Delete(mapFile);
                }
            }

            if(!isLoaded)
            {
                XMLTerrainGenerator.Run(this, inSeed, inXMLFile);

                // save map file
                FileStream stream = new FileStream(mapFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);

                stream.SetLength(0);

                using (stream)
                {
                    Routing.SaveToStream(stream);
                    Terrain.SaveToStream(stream);
                }
            }

            // add environmental objects, like trees and stones
            int count = 0;

            for (int x = 0; x < Size; x++)
            {
                for (int y = 0; y < Size; y++)
                {
                    TerrainCellFlags flags = Terrain.GetFlagsAt(x, y);
                    TerrainCellFlags flags2 = Terrain.GetFlagsAt(y, x);

                    if (flags.IsSet(TerrainCellFlags.Tree_01))
                    {
                        ResMgr.AddFoilage(new Point(x, y), FoilageType.Tree1, FoilageState.Grown);

                        count++;
                    }

                    if (flags.IsSet(TerrainCellFlags.Stone))
                        ResMgr.AddStone(new Point(x, y), m_Random.Next(0, 12));
                }
            }
        }

        public void ClearLog()
        {
            m_LogFile.Clear();
        }

        public void Save(String inFileName)
        {
            m_LogFile.Fork(CurrentCycle, inFileName);
        }

        internal BuildingConfig ResolveBuildingTypeIndex(int inBuildingID)
        {
            foreach (var race in m_Races)
            {
                var config = (from e in race.Buildables where e.TypeIndex == inBuildingID select e).FirstOrDefault();

                if (config != null)
                    return config;
            }

            return null;
        }

        public void Synchronize(Procedure inTask)
        {
            MovMgr.QueueWorkItem(inTask);
        }

        public void Synchronize(long inDueTimeMillis, Procedure inTask)
        {
            MovMgr.QueueWorkItem(inDueTimeMillis, inTask);
        }

        internal void AddBuildingInternal(BuildingConfig inConfig, Point inPosition)
        {
            m_LogFile.AddBuilding(CurrentCycle, inConfig, inPosition);
            BuildMgr.BeginBuilding(inConfig, inPosition);
        }

        public void AddBuilding(BuildingConfig inConfig, Point inPosition)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    AddBuildingInternal(inConfig, inPosition);
                });
            }
            else
            {
                AddBuildingInternal(inConfig, inPosition);
            }
        }

        internal void AddMovableInternal(CyclePoint inPosition, MovableType inMovableType)
        {
            m_LogFile.AddMovable(CurrentCycle, inPosition, inMovableType);
            MovMgr.AddMovable(inPosition, inMovableType);
        }

        public void AddMovable(CyclePoint inPosition, MovableType inMovableType)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    AddMovableInternal(inPosition, inMovableType);
                });
            }
            else
            {
                AddMovableInternal(inPosition, inMovableType);
            }
        }

        internal void DropResourceInternal(Point inAround, Resource inResource, int inCount)
        {
            m_LogFile.DropResource(CurrentCycle, inAround, inResource, inCount);
            ResMgr.DropResource(inAround, inResource, inCount);
        }

        public void DropResource(Point inAround, Resource inResource, int inCount)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    DropResourceInternal(inAround, inResource, inCount);
                });
            }
            else
            {
                DropResourceInternal(inAround, inResource, inCount);
            }
        }

        internal void AddMarketTransportInternal(long inBuildingID, Resource inResource)
        {
            m_LogFile.AddMarketTransport(CurrentCycle, inBuildingID, inResource);

            var market = (UniqueIDObject.Resolve(inBuildingID));

            if (market == null)
                market = market;

            (market as MarketBuilding).AddResourceTransport(inResource);
        }

        public void AddMarketTransport(MarketBuilding inBuilding, Resource inResource)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    AddMarketTransportInternal(inBuilding.UniqueID, inResource);
                });
            }
            else
            {
                AddMarketTransportInternal(inBuilding.UniqueID, inResource);
            }
        }

        internal void RemoveMarketTransportInternal(long inBuildingID, Resource inResource)
        {
            m_LogFile.RemoveMarketTransport(CurrentCycle, inBuildingID, inResource);
            (UniqueIDObject.Resolve(inBuildingID) as MarketBuilding).RemoveResourceTransport(inResource);
        }

        public void RemoveMarketTransport(MarketBuilding inBuilding, Resource inResource)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    RemoveMarketTransportInternal(inBuilding.UniqueID, inResource);
                });
            }
            else
            {
                RemoveMarketTransportInternal(inBuilding.UniqueID, inResource);
            }
        }

        internal void ChangeToolSettingInternal(Resource inTool, int inNewTodo, double inNewPercentage)
        {
            m_LogFile.ChangeToolSetting(CurrentCycle, inTool, inNewTodo, inNewPercentage);
            Config.ChangeToolSetting(inTool, inNewTodo, inNewPercentage);
        }

        public void ChangeToolSetting(Resource inTool, int inNewTodo, double inNewPercentage)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    ChangeToolSettingInternal(inTool, inNewTodo, inNewPercentage);
                });
            }
            else
            {
                ChangeToolSettingInternal(inTool, inNewTodo, inNewPercentage);
            }
        }

        internal void ChangeDistributionSettingInternal(String inBuildingClass, Resource inResource, bool inIsEnabled)
        {
            m_LogFile.ChangeDistributionSetting(CurrentCycle, inBuildingClass, inResource, inIsEnabled);
            Config.ChangeDistributionSetting(inBuildingClass, inResource, inIsEnabled);
        }

        public void ChangeDistributionSetting(String inBuildingClass, Resource inResource, bool inIsEnabled)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    ChangeDistributionSettingInternal(inBuildingClass, inResource, inIsEnabled);
                });
            }
            else
            {
                ChangeDistributionSettingInternal(inBuildingClass, inResource, inIsEnabled);
            }
        }

        internal void ChangeProfessionInternal(SettlerProfessions inProfession, int inDelta)
        {
            m_LogFile.ChangeProfession(CurrentCycle, inProfession, inDelta);

            Resource? tool = null;
            MovableType movType;

            switch (inProfession)
            {
                case SettlerProfessions.Constructor: tool = Resource.Hammer; movType = MovableType.Constructor; break;
                case SettlerProfessions.Grader: tool = Resource.Shovel; movType = MovableType.Grader; break;
                default:
                    throw new ArgumentException();
            }

            if (inDelta > 0)
            {
                for (int i = 0; i < inDelta; i++)
                {
                    if (tool != null)
                    {
                        // find tool 
                        var toolProv = ResMgr.FindResourceAround(new Point(0, 0), tool.Value, ResStackType.Provider);

                        if (toolProv == null)
                            return;

                        // find free settler
                        var settler = MovMgr.FindFreeMovableAround(new Point(0, 0), MovableType.Settler);

                        if (settler == null)
                            return;

                        settler.Job = new ProfessionRecruting(settler, toolProv, inProfession);
                    }
                    else
                    {
                        // find free settler
                        var settler = MovMgr.FindFreeMovableAround(new Point(0, 0), MovableType.Settler);

                        if (settler == null)
                            return;

                        // directly transform movable
                        ProfessionRecruting.TransformMovable(settler, inProfession);
                    }
                }
            }
            else
            {
                for (int i = 0; i < Math.Abs(inDelta); i++)
                {
                    // find settlers with given profession
                    var settler = MovMgr.FindFreeMovableAround(new Point(0, 0), movType);

                    if (settler == null)
                        return;

                    if (tool != null)
                    {
                        // drop tool at settler position and transform him back
                        ResMgr.DropResource(settler.Position.ToPoint(), tool.Value, 1);

                        settler.MovableType = MovableType.Settler;
                        VisualUtilities.Animate(settler, "SettlerWalking", inRestart: false, inRepeat: true);
                    }
                }
            }
        }

        public void ChangeProfession(SettlerProfessions inProfession, int inDelta)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    ChangeProfessionInternal(inProfession, inDelta);
                });
            }
            else
            {
                ChangeProfessionInternal(inProfession, inDelta);
            }
        }

        internal void RemoveBuildTaskInternal(long inTaskID)
        {
            m_LogFile.RemoveBuildTask(CurrentCycle, inTaskID);
            BuildMgr.RemoveBuildTask((UniqueIDObject.Resolve(inTaskID) as BuildTask));
        }

        public void RemoveBuildTask(BuildTask inTask)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    RemoveBuildTaskInternal(inTask.UniqueID);
                });
            }
            else
            {
                RemoveBuildTaskInternal(inTask.UniqueID);
            }
        }

        internal void SetWorkingAreaInternal(long inBuildingID, Point inWorkingCenter)
        {
            m_LogFile.SetWorkingArea(CurrentCycle, inBuildingID, inWorkingCenter);
            (UniqueIDObject.Resolve(inBuildingID) as BaseBuilding).WorkingArea = inWorkingCenter;
        }

        public void SetWorkingArea(BaseBuilding inBuilding, Point inWorkingCenter)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    SetWorkingAreaInternal(inBuilding.UniqueID, inWorkingCenter);
                });
            }
            else
            {
                SetWorkingAreaInternal(inBuilding.UniqueID, inWorkingCenter);
            }
        }

        internal void RemoveBuildingInternal(long inBuildingID)
        {
            m_LogFile.RemoveBuilding(CurrentCycle, inBuildingID);
            BuildMgr.RemoveBuilding(UniqueIDObject.Resolve(inBuildingID) as BaseBuilding);
        }

        public void RemoveBuilding(BaseBuilding inBuilding)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    RemoveBuildingInternal(inBuilding.UniqueID);
                });
            }
            else
            {
                RemoveBuildingInternal(inBuilding.UniqueID);
            }
        }

        internal void RaiseBuildingPriorityInternal(long inBuildingID)
        {
            m_LogFile.RaiseBuildingPriority(CurrentCycle, inBuildingID);
            (UniqueIDObject.Resolve(inBuildingID) as BaseBuilding).RaisePriority();
        }

        public void RaiseBuildingPriority(BaseBuilding inBuilding)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    RaiseBuildingPriorityInternal(inBuilding.UniqueID);
                });
            }
            else
            {
                RaiseBuildingPriorityInternal(inBuilding.UniqueID);
            }
        }

        internal void LowerBuildingPriorityInternal(long inBuildingID)
        {
            m_LogFile.LowerBuildingPriority(CurrentCycle, inBuildingID);
            (UniqueIDObject.Resolve(inBuildingID) as BaseBuilding).LowerPriority();
        }

        public void LowerBuildingPriority(BaseBuilding inBuilding)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    LowerBuildingPriorityInternal(inBuilding.UniqueID);
                });
            }
            else
            {
                LowerBuildingPriorityInternal(inBuilding.UniqueID);
            }
        }

        internal void SetBuildingSuspendedInternal(long inBuildingID, bool isSuspended)
        {
            m_LogFile.SetBuildingSuspended(CurrentCycle, inBuildingID, isSuspended);
            (UniqueIDObject.Resolve(inBuildingID) as BaseBuilding).IsSuspended = isSuspended;
        }

        public void SetBuildingSuspended(BaseBuilding inBuilding, bool isSuspended)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    SetBuildingSuspendedInternal(inBuilding.UniqueID, isSuspended);
                });
            }
            else
            {
                SetBuildingSuspendedInternal(inBuilding.UniqueID, isSuspended);
            }
        }

        internal void RaiseTaskPriorityInternal(long inTaskID)
        {
            m_LogFile.RaiseTaskPriority(CurrentCycle, inTaskID);
            BuildMgr.RaiseTaskPriority((UniqueIDObject.Resolve(inTaskID) as BuildTask));
        }

        public void RaiseTaskPriority(BuildTask inTask)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    RaiseTaskPriorityInternal(inTask.UniqueID);
                });
            }
            else
            {
                RaiseTaskPriorityInternal(inTask.UniqueID);
            }
        }

        internal void SetTaskSuspendedInternal(long inTaskID, bool isSuspended)
        {
            m_LogFile.SetTaskSuspended(CurrentCycle, inTaskID, isSuspended);
            (UniqueIDObject.Resolve(inTaskID) as BuildTask).IsSuspended = isSuspended;
        }

        public void SetTaskSuspended(BuildTask inTask, bool isSuspended)
        {
            if (IsInitialized)
            {
                Synchronize(() =>
                {
                    SetTaskSuspendedInternal(inTask.UniqueID, isSuspended);
                });
            }
            else
            {
                SetTaskSuspendedInternal(inTask.UniqueID, isSuspended);
            }
        }

        internal void AddOneCycle()
        {
            if (!IsInitialized)
                throw new InvalidOperationException();

            MovMgr.CurrentCycle++;

            BuildMgr.ProcessCycle();
            ResMgr.ProcessCycle();
            MovMgr.ProcessCycle();

            if (m_UpdateConfig)
            {
                m_UpdateConfig = false;

                Config.Update();
            }
        }

        private void SimulationLoop()
        {
            try
            {
                var watch = new Stopwatch();
                long lastElapsed = 0;
                long elapsedShift = 0;
                long cycleMillis = 0;

                if (System.IO.File.Exists(".\\GameLog.s3g"))
                {
                    try
                    {
                        // load game...
                        long cycle = 0;
                        GameMapFile state = null;

                        using (state = GameMapFile.OpenRead(".\\GameLog.s3g"))
                        {

                            for (; cycle < state.NextCycle; cycle++)
                            {
                                AnimationTime += (long)CyclePoint.CYCLE_MILLIS;
                                AddOneCycle();
                            }

                            while (state.NextCycle >= 0)
                            {
                                long thisCycle = state.NextCycle;

                                while (state.NextCycle == thisCycle)
                                {
                                    state.ReadNext(this);
                                }

                                for (; cycle < state.NextCycle; cycle++)
                                {
                                    AnimationTime += (long)CyclePoint.CYCLE_MILLIS;
                                    AddOneCycle();
                                }
                            }
                        }

                        cycleMillis = (long)(CyclePoint.CYCLE_MILLIS * CurrentCycle);
                        AnimationTime = cycleMillis;
                        elapsedShift = cycleMillis;
                        lastElapsed = cycleMillis;
                    }
                    catch (Exception e)
                    {
                        Log.LogExceptionCritical(e);
                    }
                }

                if (OnGameStarted != null)
                    OnGameStarted(this);

                watch.Start();


                while (true)
                {
                    long elapsed = watch.ElapsedMilliseconds + elapsedShift;

                    try
                    {
                        if (m_ForwardOneMinute)
                        {
                            elapsedShift += (long)TimeSpan.FromMinutes(1).TotalMilliseconds;
                            m_ForwardOneMinute = false;
                        }

                        if (elapsed - lastElapsed < 10)
                        {
                            System.Threading.Thread.Sleep(10);

                            continue;
                        }

                        for (; cycleMillis < elapsed; cycleMillis += (int)CyclePoint.CYCLE_MILLIS)
                        {
                            AnimationTime += (long)CyclePoint.CYCLE_MILLIS;
                            AddOneCycle();

                            if (IsSuspended)
                            {
                                long backup = watch.ElapsedMilliseconds;

                                m_SuspendSignal.Set();
                                m_ResumeSignal.WaitOne();

                                elapsedShift -= watch.ElapsedMilliseconds - backup;
                            }
                        }
                    }
                    finally
                    {
                        lastElapsed = elapsed;
                    }
                }
            }
            catch (Exception e)
            {
                Log.LogExceptionCritical(e);
            }
        }

        private void Movable_OnStop(Movable inMovable)
        {

            if (inMovable.Carrying.HasValue)
            {
                DropResource(inMovable.Position.ToPoint(), inMovable.Carrying.Value, 1);

                inMovable.Carrying = null;
            }
        }

        public void SuspendGame()
        {
            if (IsSuspended)
                return;

            m_SuspendSignal.Reset();
            m_ResumeSignal.Reset();

            IsSuspended = true;

            // wait for signal
            m_SuspendSignal.WaitOne();
        }

        public void ResumeGame()
        {
            IsSuspended = false;

            m_ResumeSignal.Set();
        }
    }
}
